#include "WinSystem.h"

#include "Input.h"
#include "Graphix.h"
#include "Util.h"

#include <assert.h>

namespace TestDX
{

// Constants get value. TODO: Use configuration class
const int WinSystem::SCREEN_WIDTH = 1024;
const int WinSystem::SCREEN_HEIGHT = 768;

WinSystem::WinSystem() :
	_appName(L"TestDX"),
	_hInstance(NULL),
	_hWnd(NULL),
	_pInput(NULL),
	_pGraphics(NULL)
{
}


WinSystem::~WinSystem()
{
	Shutdown();
}

bool WinSystem::Init()
{
	int width = 0, height = 0;

	InitWinApi(width, height);

	_pInput = new Input;
	if(!_pInput)
	{
		// @ TODO: Log
		return false;
	}
	
	if(!_pInput->Init(_hInstance, _hWnd))
	{
		// @ TODO: Log
		return false;
	}

	_pGraphics = new Graphix;
	if(!_pGraphics)
	{
		// @ TODO: Log
		return false;
	}

	if(!_pGraphics->Init(width, height, (void *)_hWnd))
	{
		// @ TODO: Log
		return false;
	}

	return true;
}

void WinSystem::InitWinApi(int & width, int & height)
{
	// @ TODO: Fullscreen?

	// @ TODO: This sucks, turn WinSystem into a Singleton to avoid having multiple instances?
	__pApp = this;

	// Get this application's own module handle
	_hInstance = GetModuleHandle(NULL);
	
	assert(_hInstance); // @ TODO: Temp solution for failing
	if(!_hInstance)
	{
		// @ TODO: Get error msg if possible and properly quit
	}

	// Setup window class
	WNDCLASSEX wc;
	ZeroMemory(&wc, sizeof(WNDCLASSEX));
	wc.cbSize = sizeof(WNDCLASSEX);
	wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
	wc.lpfnWndProc = WndProc;
	wc.cbClsExtra = 0;
	wc.cbWndExtra = 0;
	wc.hInstance = _hInstance;
	wc.hIcon = NULL; // Default icon
	wc.hIconSm = wc.hIcon;
	wc.hCursor = LoadCursor(NULL, IDC_ARROW);
	wc.hbrBackground = (HBRUSH) GetStockObject(BLACK_BRUSH);
	wc.lpszMenuName = NULL;
	wc.lpszClassName = _appName; // class name, also used as window name when creating

	RegisterClassEx(&wc);

	width = SCREEN_WIDTH;
	height = SCREEN_HEIGHT;

	// Place window in the middle of the screen
	int posX = (GetSystemMetrics(SM_CXSCREEN) - width) / 2;
	int posY = (GetSystemMetrics(SM_CYSCREEN) - height) / 2;

	_hWnd = CreateWindowEx(
		WS_EX_APPWINDOW,
		_appName, // class name
		_appName, // window name
		WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_TILEDWINDOW,
		posX, posY,
		width, height,
		NULL, // parent
		NULL, // menu
		_hInstance,
		NULL); // lpParam. TODO: Can be used to pass this pointer for message handling
		
	assert(_hWnd); // @ TODO: Temp solution for failing
	if(!_hWnd)
	{
		// @ TODO: Call GetLastError to get error msg, then log and properly quit
	}

	ShowWindow(_hWnd, SW_SHOW);
	SetForegroundWindow(_hWnd);
	SetFocus(_hWnd);

	// Not needed
	//ShowCursor(true);
}

void WinSystem::Shutdown()
{
	SAFE_DELETE(_pInput);
	SAFE_DELETE(_pGraphics);

	ShutdownWinApi();
}

void WinSystem::ShutdownWinApi()
{
	ShowCursor(true);

	DestroyWindow(_hWnd);
	_hWnd = NULL;

	UnregisterClass(_appName, _hInstance);
	_hInstance = NULL;

	__pApp = NULL;
}


void WinSystem::Run()
{
	MSG msg;
	ZeroMemory(&msg, sizeof(MSG));

	bool finish = false;
	while(!finish)
	{
		if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
		{
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}

		// We deal with WM_QUIT message
		if(msg.message == WM_QUIT)
		{
			finish = true;
		}
		else
		{
			// Process frame
			if(!Frame())
			{
				MessageBox(_hWnd, L"Frame processing failed!", L"Error", MB_OK);
				finish = true;
			}
		}
	
		// ESC key means user wants to exit
		if(_pInput->IsEscapePressed())
		{
			finish = true;
		}
	}
}

bool WinSystem::Frame()
{
	int mouseDX, mouseDY;

	if(!_pInput->Frame())
	{
		// @ TODO: Log
	}

	_pInput->GetMouseMovement(mouseDX, mouseDY);

	// @ TODO: Send input to graphics, which also renders
	return _pGraphics->Frame(mouseDX, mouseDY);
}

LRESULT CALLBACK WinSystem::MsgHandler(
	HWND hwnd,
	UINT umsg,
	WPARAM wparam,
	LPARAM lparam)
{
	return DefWindowProc(hwnd, umsg, wparam, lparam);	
}

LRESULT CALLBACK WndProc(
	HWND hwnd,
	UINT umsg,
	WPARAM wparam,
	LPARAM lparam)
{
	switch(umsg)
	{
	case WM_DESTROY:
	case WM_CLOSE:
		PostQuitMessage(0);
		break;

	default:
		return __pApp->MsgHandler(hwnd, umsg, wparam, lparam);
	}

	return 0;
}

}